package com.laijiale.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.DesensitizedUtil;
import cn.hutool.core.util.IdcardUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.crypto.SecureUtil;
import com.laijiale.component.MyRedis;
import com.laijiale.constant.ML;
import com.laijiale.dto.*;

import com.laijiale.entity.*;
import com.laijiale.exception.ServiceException;
import com.laijiale.mapper.UserRoleMapper;
import com.laijiale.result.Result;
import com.laijiale.until.JwtUtil;
import com.laijiale.until.MinioUtil;
import com.laijiale.until.MyUtil;
import com.laijiale.until.UserUtil;
import com.laijiale.vo.LoginVO;
import com.laijiale.vo.UserSimpleListVO;
import com.mybatisflex.core.query.QueryChain;
import com.mybatisflex.core.query.QueryMethods;
import com.mybatisflex.core.relation.RelationManager;
import com.mybatisflex.core.update.UpdateChain;
import com.mybatisflex.spring.service.impl.ServiceImpl;
import com.laijiale.mapper.UserMapper;
import com.laijiale.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.mybatisflex.core.paginate.Page;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import static com.laijiale.entity.table.MenuTableDef.MENU;
import static com.laijiale.entity.table.RoleMenuTableDef.ROLE_MENU;
import static com.laijiale.entity.table.RoleTableDef.ROLE;
import static com.laijiale.entity.table.UserRoleTableDef.USER_ROLE;
import static com.laijiale.entity.table.UserTableDef.USER;
import static com.mybatisflex.core.query.QueryMethods.*;

/**
 * 用户表 服务层实现。
 *
 * @author laijiale
 * @since 2024-12-22
 */
@Service
@CacheConfig(cacheNames = "user")
public class UserServiceImpl extends ServiceImpl<UserMapper, User>  implements UserService {
    @Autowired
    private UserRoleMapper userRoleMapper;
    @Autowired
    private MyRedis redis;

    @CacheEvict(allEntries = true)
    @Override
    public boolean insert(UserInsertDTO dto) {
        // 校验身份证号
        String idcard = dto.getIdcard();
        if (!IdcardUtil.isValidCard(idcard)) {
            throw new RuntimeException("身份证号格式有误");
        }

        //账号查重
        if (QueryChain.of(mapper)
                .where(USER.USERNAME.eq(dto.getUsername()))
                .exists()) {
            throw new RuntimeException("账号已存在");
        }

        //身份证号查重
        if (QueryChain.of(mapper)
                .where(USER.IDCARD.eq(dto.getIdcard()))
                .exists()) {
            throw new RuntimeException("身份证号已存在");
        }
        //电话号码查重
        if (QueryChain.of(mapper)
                .where(USER.PHONE.eq(dto.getPhone()))
                .exists()) {
            throw new RuntimeException("手机号码己存在");
        }
        //电子邮箱查量
        if (QueryChain.of(mapper)
                .where(USER.EMAIL.eq(dto.getEmail()))
                .exists()) {
            throw new RuntimeException("电子邮箱己存在");
        }
        // 组装 entity 实体类
        User user = BeanUtil.copyProperties(dto, User.class);
        user.setNickname(RandomUtil.randomString(10));
        user.setGender(UserUtil.defaultGender(idcard));
        user.setAge(UserUtil.defaultAge(idcard));
        user.setZodiac(UserUtil.defaultZodiac(idcard));
        user.setAvatar(UserUtil.defaultAvatar(idcard));
        user.setProvince(UserUtil.defaultProvince(idcard));
        user.setInfo("该用户很懒，暂无任何描述。");
        user.setPassword(SecureUtil.md5(user.getPassword()));
        user.setCreated(LocalDateTime.now());
        user.setUpdated(LocalDateTime.now());
        return mapper.insert(user) > 0;
    }



    @Cacheable(key = "#root.methodName + ':' + #p0",
            condition = "#po != null",
            unless = "#result == null")
    @Override
    public List<Long> listRoleIds(Long id) {
        return
                QueryChain.of(UserRole.class)
                        .select(USER_ROLE.FK_ROLE_ID)
                        .where(USER_ROLE.FK_USER_ID.eq(id))
                        .objListAs(Long.class);
    }

    @CacheEvict(allEntries = true)
    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean updateRoles(UserUpdateDTO dto) {
        Long userId = dto.getId();
        List<Long> roleIds = dto.getRoleIds();
        //在中间表中删除该用户的全部角色记录
        UpdateChain.of(userRoleMapper)
                .where(USER_ROLE.FK_USER_ID.eq(userId))
                .remove();
        //新角色列表为空，直接返回即可
        if (CollUtil.isEmpty(roleIds)) {
            return true;
        }
        //在中间表中批量添加该用户的新角色记录
        List<UserRole> userRoles = new ArrayList<>();
        for (Long roleId : roleIds) {
            UserRole userRole = new UserRole();
            userRole.setFkUserId(userId);
            userRole.setFkRoleId(roleId);
            userRole.setCreated(LocalDateTime.now());
            userRole.setUpdated(LocalDateTime.now());
            userRoles.add(userRole);
        }
        return userRoleMapper.insertBatch(userRoles) == roleIds.size();

    }

    @CacheEvict(allEntries = true)
    @Override
    public boolean resetPassword(Long id) {
        return UpdateChain.of(mapper)
                .set(USER.PASSWORD, SecureUtil.md5(ML.User.DEFAULT_PASSWORD))
                .where(USER.ID.eq(id))
                .update();

    }

    @CacheEvict(allEntries = true)
    @Override
    public boolean updatePassword(UserUpdatePasswordDTO dto) {
        Long userId = dto.getId();
        //判断用户是否存在以及原密码是否正确
        if (!QueryChain.of(mapper)
                .where(USER.ID.eq(userId))
                .and(USER.PASSWORD.eq(SecureUtil.md5(dto.getOldPassword())))
                .exists()) {
            throw new ServiceException("用户不存在或原密码有误");
        }
        //根据用户主键修改密码
        return UpdateChain.of(mapper)
                .set(USER.PASSWORD, SecureUtil.md5(dto.getNewPassword()))
                .where(USER.ID.eq(userId))
                .update();
    }

    @Override

    public Map<String, Object> statistics() {

        Map<String, Object> result = new HashMap<>();

        //统计用户性别比例

        // select gender as name, count(*) as value from ^user` group by gender

        result.put("genderCount", QueryChain.of(mapper)

                .select(USER.GENDER.as("name"), QueryMethods.count().as("value"))

                .groupBy(USER.GENDER)

                .orderBy(USER.GENDER.asc())

                .listAs(Map.class));

        //统计今日用户数

        // select count(*) from ^user^ where datediff(curdate(), date_format(created, '%Y-%m-%d')) = (

        double todayCount = QueryChain.of(mapper)

                .where(dateDiff(currentDate(), dateFormat(USER.CREATED, "%Y-%m-%d")).eq(0))

                .count();

        //统计昨日用户数

        // select count(*) from ^user^ where datediff(curdate(), date_format(created, '%Y-%m-%d'))

        double yesterdayCount = QueryChain.of(mapper)

                .where(dateDiff(currentDate(), dateFormat(USER.CREATED, "%Y-%m-%d")).eq(1))

                .count();

        //统计今年用户数

        // select count(*) from ^user^ where year(created) = year(current_date);

        double thisYearCount = QueryChain.of(mapper)

                .where(year(USER.CREATED).eq(year(currentDate())))

                .count();

        //统计去年用户总数

        // select count(*) from ^user` where year(created) - year(current_date) = -1;

        double lastYearCount = QueryChain.of(mapper)

                .where(year(USER.CREATED).subtract(year(currentDate())).eq(-1))

                .count();

        result.put("todayCount", todayCount);

        result.put("yesterdaycount", yesterdayCount);

        result.put("dayIncrease", MyUtil.increase(todayCount, yesterdayCount));

        result.put("thisYearCount", thisYearCount);

        result.put("lastYearCount", lastYearCount);

        result.put("yearIncrease", MyUtil.increase(thisYearCount, lastYearCount));

        return result;

    }

    @Override

    public List<UserExcelDTO> excel() {

        //查询全部用户记录

        List<User> users = QueryChain.of(mapper).list();

        // List<User> -> List<UserExcelDTO>

        List<UserExcelDTO> result = new ArrayList<>();

        users.forEach(user -> {

            //拷贝用户基础信息

            UserExcelDTO userExcelDTO = BeanUtil.copyProperties(user, UserExcelDTO.class);

            //处理性别：0女1男2保密

            userExcelDTO.setGender(ML.User.genderFormat(user.getGender()));

            //数据脱敏

            userExcelDTO.setRealname(DesensitizedUtil.chineseName(user.getRealname()));

            userExcelDTO.setIdcard(DesensitizedUtil.idCardNum(user.getIdcard(), 6, 3));

            userExcelDTO.setPhone(DesensitizedUtil.mobilePhone(user.getPhone()));

            result.add(userExcelDTO);

        });

        return result;

    }

    @Override

    public String getVcode(String phone) {

        //根据手机号码查询一名用户

        User user = QueryChain.of(mapper).where(USER.PHONE.eq(phone)).one();

        // 检查手机号码是否存在
        if (!QueryChain.of(mapper)
                .where(USER.PHONE.eq(phone))
                .exists()) {
            throw new ServiceException("手机号码不存在");
        }

        //将短信验证码存入redis中，有效期5分钟

        String key = ML.Redis.VCODE_PREFIX + phone;

        String val = RandomUtil.randomNumbers(6);

        redis.setEx(key, val, 5, TimeUnit.MINUTES);

        //todo：向指定手机号码发送验证码

        //将短信验证码返回给客户端

        return val;

    }

    @Override
    public LoginVO loginByPhone(LoginByPhoneDTO dto) {
        //校验验证码是否有效
        String key = ML.Redis.VCODE_PREFIX + dto.getPhone();
        String vcodeFromRedis = redis.get(key);
        if (ObjectUtil.isNull(vcodeFromRedis) || !vcodeFromRedis.equals(dto.getVcode())) {
            throw new ServiceException("验证码无效");
        }
        //    //根据手机号码查询用户记录
        //            // select * from user where phone =
        User user = QueryChain.of(mapper)
                .where(USER.PHONE.eq(dto.getPhone()))
                .one();
        if (ObjectUtil.isNull(user)) {
            throw new ServiceException("手机号码不存在");

        }
        return buildLoginVO(user);
    }

        /**组装LoginVo
    @param user 用户实体
    @return Loginvo：包含用户信息，对应该用户的Token令牌，角色标题列表以及菜单列表
    */
        private LoginVO buildLoginVO(User user){
            List<Long> roleIds = QueryChain.of(UserRole.class)
                    .select(USER_ROLE.FK_ROLE_ID)
                    .where(USER_ROLE.FK_USER_ID.eq(user.getId()))
                    .objListAs(Long.class);
            List<String> roleTitles = QueryChain.of(Role.class)
                    .select(ROLE.TITLE)
                    .where(ROLE.ID.in(roleIds))
                    .objListAs(String.class);
            List<Long> menuIds = QueryChain.of(RoleMenu.class)
                    .select(ROLE_MENU.FK_MENU_ID)
                    .where(ROLE_MENU.FK_ROLE_ID.eq(user.getId()))
                    .objListAs(Long.class);
            RelationManager.addIgnoreRelations("parentMenu");
            List<Menu> menus = QueryChain.of(Menu.class)
                    .where(MENU.ID.in(menuIds))
                    .and(MENU.PID.eq(ML.Menu.ROOT_ID))
                    .orderBy(MENU.IDX.asc(),MENU.ID.desc())
                    .withRelations()
                    .list();
            LoginVO result = new LoginVO();
            result.setRoleTitles(roleTitles);
            result.setMenus(menus);
            result.setUser(UserUtil.desensitization(user));
            result.setToken(JwtUtil.build(user.getId(),user.getNickname(),user.getAvatar()));
            return result;
        }



        @Cacheable(key = "#root.methodName")
            @Override
    public Page<User> page(UserPageDTO dto) {

        QueryChain<User> queryChain = QueryChain.of(mapper);

        // username条件

        String username = dto.getUsername();

        if (ObjectUtil.isNotNull(username)) {

            queryChain.where(USER.USERNAME.like(username) );

        }

        // nickname条件

        String nickname = dto.getNickname();

        if (ObjectUtil.isNotNull(nickname)) {

            queryChain.where(USER.NICKNAME.like(nickname));

        }

        //phone条件

        String phone = dto.getPhone();

        if (ObjectUtil.isNotNull(phone)) {

            queryChain.where(USER.PHONE.like(phone));

        }

        //分页查询

        Page<User> result = queryChain.withRelations()

                .page(new Page<>(dto.getPageNum(), dto.getPageSize()));

        //脱敏

        result.setRecords(UserUtil.desensitization(result.getRecords()));

        return result;

    }



    @Cacheable(key = "#p0", condition = "#p0 != null", unless = "#result == null")

    @Override

    public User select(Long id) {

        return UserUtil.desensitization(QueryChain.of(mapper)

                .where(USER.ID.eq(id))

                .withRelations()

                .one());

    }



    @CacheEvict(allEntries = true)

    @Override

    public boolean update(UserUpdateDTO dto) {

        //电子邮箱查重

        if (QueryChain.of(mapper)

                .where(USER.EMAIL.eq(dto.getEmail()))

                .exists()) {

            throw new ServiceException("电子邮箱已存在");

        }

        //组装 entity 实体类

        User user = BeanUtil.copyProperties(dto, User.class);

        user.setUpdated(LocalDateTime.now());

        return UpdateChain.of(user)

                .where(USER.ID.eq(user.getId()))

                .update();



    }



    @Override

    @CacheEvict(allEntries = true)

    public boolean delete(Long id) {

        return UpdateChain.of(mapper)

                .where(USER.ID.eq(id))

                .remove();

    }



    @Override

    @CacheEvict(allEntries = true)

    public boolean deleteBatch(List<Long> ids) {

        return UpdateChain.of(mapper)

                .where(USER.ID.in(ids))

                .remove();

    }
    @Override
    public LoginVO login(LoginDTO dto) {

        User user=QueryChain.of(mapper)
                .where(USER.USERNAME.eq(dto.getUsername()))
                .and(USER.PASSWORD.eq(SecureUtil.md5(dto.getPassword())))
                .one();
        //检验账号密码
        if(ObjectUtil.isNull(user)){
            throw new ServiceException("账号或密码错误");
        }
        //生成token令牌
        String token = JwtUtil.build(user.getId(), user.getRealname(), user.getAvatar());

        user.setRealname(DesensitizedUtil.chineseName(user.getRealname()));
        user.setIdcard(DesensitizedUtil.idCardNum(user.getIdcard(), 6, 3));
        user.setPhone(DesensitizedUtil.mobilePhone(user.getPhone()));

        return buildLoginVO(user);
    }

    @Override
    @CacheEvict(allEntries = true)
    public Result<String> uploadAvatar(MultipartFile avatarFile, Long id) {
        //按主键查询用户记录
        User user = mapper.selectOneById(id);
        if (ObjectUtil.isNull(user)) {
            throw new ServiceException("用户不存在");
        }
            //备份旧头像
            String oldAvatar = user.getAvatar();
            //重新生成文件名
            String filename = MinioUtil.randomFilename(avatarFile);
            //根据用户主键修改用户头像
            user.setAvatar(filename);
            if (mapper.update(user) <= 0) {
                throw new ServiceException("头像更新失败：DB操作异常");
            }
                try {
                    //从MinIO中删除旧头像：默认头像不删除
                    if (!ML.User.DEFAULT_AVATARS.contains(oldAvatar)) {
                        MinioUtil.delete(oldAvatar, ML.Minio.AVATAR_DIR, ML.Minio.BUCKET_NAME);
                        //向MinIO中上传新头像
                        MinioUtil.upload(avatarFile, filename, ML.Minio.AVATAR_DIR, ML.Minio.BUCKET_NAME);
                    }
                } catch (Exception e) {
                    throw new ServiceException("头像更新失败：MinIo操作异常");
                }
                //返回文件名
                return new Result<>(filename);
            }
    @Cacheable(key = "#root.methodName",
            unless = "#result == null")
    @Override
    public List<UserSimpleListVO> simpleList() {
        List<UserSimpleListVO> userSimpleListVOS =QueryChain.of(mapper).orderBy(USER.ID.asc()
        ).withRelations().listAs(UserSimpleListVO.class);
        return userSimpleListVOS;
    }
}





